home *** CD-ROM | disk | FTP | other *** search
- """SMTP Client class.
-
- Author: The Dragon De Monsyne <dragondm@integral.org>
-
- (This was modified from the Python 1.5 library HTTP lib.)
-
- This should follow RFC 821. (it dosen't do esmtp (yet))
-
- Example:
-
- >>> import smtplib
- >>> s=smtplib.SMTP("localhost")
- >>> print s.help()
- This is Sendmail version 8.8.4
- Topics:
- HELO EHLO MAIL RCPT DATA
- RSET NOOP QUIT HELP VRFY
- EXPN VERB ETRN DSN
- For more info use "HELP <topic>".
- To report bugs in the implementation send email to
- sendmail-bugs@sendmail.org.
- For local information send email to Postmaster at your site.
- End of HELP info
- >>> s.putcmd("vrfy","someone@here")
- >>> s.getreply()
- (250, "Somebody OverHere <somebody@here.my.org>")
- >>> s.quit()
-
- """
-
- import socket
- import string,re
-
- SMTP_PORT = 25
- CRLF="\r\n"
-
- # used for exceptions
- SMTPServerDisconnected="Server not connected"
- SMTPSenderRefused="Sender address refused"
- SMTPRecipientsRefused="All Recipients refused"
- SMTPDataError="Error transmoitting message data"
-
- class SMTP:
- """This class manages a connection to an SMTP server."""
-
- def __init__(self, host = '', port = 0):
- """Initialize a new instance.
-
- If specified, `host' is the name of the remote host to which
- to connect. If specified, `port' specifies the port to which
- to connect. By default, smtplib.SMTP_PORT is used.
-
- """
- self.debuglevel = 0
- self.file = None
- self.helo_resp = None
- if host: self.connect(host, port)
-
- def set_debuglevel(self, debuglevel):
- """Set the debug output level.
-
- A non-false value results in debug messages for connection and
- for all messages sent to and received from the server.
-
- """
- self.debuglevel = debuglevel
-
- def connect(self, host='localhost', port = 0):
- """Connect to a host on a given port.
-
- Note: This method is automatically invoked by __init__,
- if a host is specified during instantiation.
-
- """
- if not port:
- i = string.find(host, ':')
- if i >= 0:
- host, port = host[:i], host[i+1:]
- try: port = string.atoi(port)
- except string.atoi_error:
- raise socket.error, "nonnumeric port"
- if not port: port = SMTP_PORT
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- if self.debuglevel > 0: print 'connect:', (host, port)
- self.sock.connect(host, port)
- (code,msg)=self.getreply()
- if self.debuglevel >0 : print "connect:", msg
- return msg
-
- def send(self, str):
- """Send `str' to the server."""
- if self.debuglevel > 0: print 'send:', `str`
- if self.sock:
- self.sock.send(str)
- else:
- raise SMTPServerDisconnected
-
- def putcmd(self, cmd, args=""):
- """Send a command to the server.
- """
- str = '%s %s%s' % (cmd, args, CRLF)
- self.send(str)
-
- def getreply(self):
- """Get a reply from the server.
-
- Returns a tuple consisting of:
- - server response code (e.g. '250', or such, if all goes well)
- Note: returns -1 if it can't read responce code.
- - server response string corresponding to response code
- (note : multiline responces converted to a single,
- multiline string)
- """
- resp=[]
- self.file = self.sock.makefile('rb')
- while 1:
- line = self.file.readline()
- if self.debuglevel > 0: print 'reply:', `line`
- resp.append(string.strip(line[4:]))
- code=line[:3]
- #check if multiline resp
- if line[3:4]!="-":
- break
- try:
- errcode = string.atoi(code)
- except(ValueError):
- errcode = -1
-
- errmsg = string.join(resp,"\n")
- if self.debuglevel > 0:
- print 'reply: retcode (%s); Msg: %s' % (errcode,errmsg)
- return errcode, errmsg
-
- def docmd(self, cmd, args=""):
- """ Send a command, and return it's responce code """
-
- self.putcmd(cmd,args)
- (code,msg)=self.getreply()
- return code
- # std smtp commands
-
- def helo(self, name=''):
- """ SMTP 'helo' command. Hostname to send for this command
- defaults to the FQDN of the local host """
- name=string.strip(name)
- if len(name)==0:
- name=socket.gethostbyaddr(socket.gethostname())[0]
- self.putcmd("helo",name)
- (code,msg)=self.getreply()
- self.helo_resp=msg
- return code
-
- def help(self, args=''):
- """ SMTP 'help' command. Returns help text from server """
- self.putcmd("help", args)
- (code,msg)=self.getreply()
- return msg
-
- def rset(self):
- """ SMTP 'rset' command. Resets session. """
- code=self.docmd("rset")
- return code
-
- def noop(self):
- """ SMTP 'noop' command. Dosen't do anything :> """
- code=self.docmd("noop")
- return code
-
- def mail(self,sender):
- """ SMTP 'mail' command. Begins mail xfer session. """
- self.putcmd("mail","from: %s" % sender)
- return self.getreply()
-
- def rcpt(self,recip):
- """ SMTP 'rcpt' command. Indicates 1 recipient for this mail. """
- self.putcmd("rcpt","to: %s" % recip)
- return self.getreply()
-
- def data(self,msg):
- """ SMTP 'DATA' command. Sends message data to server.
- Automatically quotes lines beginning with a period per rfc821 """
- #quote periods in msg according to RFC821
- # ps, I don't know why I have to do it this way... doing:
- # quotepat=re.compile(r"^[.]",re.M)
- # msg=re.sub(quotepat,"..",msg)
- # should work, but it dosen't (it doubles the number of any
- # contiguous series of .'s at the beginning of a line,
- #instead of just adding one. )
- quotepat=re.compile(r"^[.]+",re.M)
- def m(pat):
- return "."+pat.group(0)
- msg=re.sub(quotepat,m,msg)
- self.putcmd("data")
- (code,repl)=self.getreply()
- if self.debuglevel >0 : print "data:", (code,repl)
- if code <> 354:
- return -1
- else:
- self.send(msg)
- self.send("\n.\n")
- (code,msg)=self.getreply()
- if self.debuglevel >0 : print "data:", (code,msg)
- return code
-
- #some usefull methods
- def sendmail(self,from_addr,to_addrs,msg):
- """ This command performs an entire mail transaction.
- The arguments are:
- - from_addr : The address sending this mail.
- - to_addrs : a list of addresses to send this mail to
- - msg : the message to send.
-
- This method will return normally if the mail is accepted for at least
- one recipiant.
- Otherwise it will throw an exception (either SMTPSenderRefused,
- SMTPRecipientsRefused, or SMTPDataError)
-
- That is, if this method does not throw an exception, then someone
- should get your mail.
-
- It returns a dictionary , with one entry for each recipient that was
- refused.
-
- example:
-
- >>> import smtplib
- >>> s=smtplib.SMTP("localhost")
- >>> tolist=["one@one.org","two@two.org","three@three.org","four@four.org"]
- >>> msg = '''
- ... From: Me@my.org
- ... Subject: testin'...
- ...
- ... This is a test '''
- >>> s.sendmail("me@my.org",tolist,msg)
- { "three@three.org" : ( 550 ,"User unknown" ) }
- >>> s.quit()
-
- In the above example, the message was accepted for delivery to
- three of the four addresses, and one was rejected, with the error
- code 550. If all addresses are accepted, then the method
- will return an empty dictionary.
- """
-
- if not self.helo_resp:
- self.helo()
- (code,resp)=self.mail(from_addr)
- if code <>250:
- self.rset()
- raise SMTPSenderRefused
- senderrs={}
- for each in to_addrs:
- (code,resp)=self.rcpt(each)
- if (code <> 250) and (code <> 251):
- senderrs[each]=(code,resp)
- if len(senderrs)==len(to_addrs):
- #th' server refused all our recipients
- self.rset()
- raise SMTPRecipientsRefused
- code=self.data(msg)
- if code <>250 :
- self.rset()
- raise SMTPDataError
- #if we got here then somebody got our mail
- return senderrs
-
-
- def close(self):
- """Close the connection to the SMTP server."""
- if self.file:
- self.file.close()
- self.file = None
- if self.sock:
- self.sock.close()
- self.sock = None
-
-
- def quit(self):
- self.docmd("quit")
- self.close()
-